11wk-2: Choropleth (plotly)

plotly
Author

최규빈

Published

November 15, 2023

1. 강의영상

2. Imports

# !pip install folium
import numpy as np
import pandas as pd
#---#
import plotly.express as px
import json 
import requests 

3. 에너지사용량 시각화

A. 데이터 불러오기

global_dict = json.loads(requests.get('https://raw.githubusercontent.com/southkorea/southkorea-maps/master/kostat/2018/json/skorea-provinces-2018-geo.json').text)
local_dict = json.loads(requests.get('https://raw.githubusercontent.com/southkorea/southkorea-maps/master/kostat/2018/json/skorea-municipalities-2018-geo.json').text)
#--#
url = 'https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/{}.csv'
prov = ['Seoul', 'Busan', 'Daegu', 'Incheon', 
        'Gwangju', 'Daejeon', 'Ulsan', 'Sejongsi', 
        'Gyeonggi-do', 'Gangwon-do', 'Chungcheongbuk-do', 
        'Chungcheongnam-do', 'Jeollabuk-do', 'Jeollanam-do', 
        'Gyeongsangbuk-do', 'Gyeongsangnam-do', 'Jeju-do']
df = pd.concat([pd.read_csv(url.format(p+y)).assign(년도=y, 시도=p) for p in prov for y in ['2018', '2019', '2020', '2021']]).reset_index(drop=True)\
.assign(년도 = lambda df: df.년도.astype(int))\
.set_index(['년도','시도','지역']).applymap(lambda x: int(str(x).replace(',','')))\
.reset_index()
df.head()
/tmp/ipykernel_2404948/712135142.py:12: FutureWarning:

DataFrame.applymap has been deprecated. Use DataFrame.map instead.
년도 시도 지역 건물동수 연면적 에너지사용량(TOE)/전기 에너지사용량(TOE)/도시가스 에너지사용량(TOE)/지역난방
0 2018 Seoul 종로구 17929 9141777 64818 82015 111
1 2018 Seoul 중구 10598 10056233 81672 75260 563
2 2018 Seoul 용산구 17201 10639652 52659 85220 12043
3 2018 Seoul 성동구 14180 11631770 60559 107416 0
4 2018 Seoul 광진구 21520 12054796 70609 130308 0

B. 데이터정리

(1) global_dict 내의 영어이름과 df의 영어이름이 일치하는지 확인

set(df.시도) == {l['properties']['name_eng'] for l in global_dict['features']}
True

(2) global_dict내의 영어이름과 한글이름을 이용해 변환을 위한 dictionary 생성

{l['properties']['name_eng']:l['properties']['name'] for l in global_dict['features']}
{'Seoul': '서울특별시',
 'Busan': '부산광역시',
 'Daegu': '대구광역시',
 'Incheon': '인천광역시',
 'Gwangju': '광주광역시',
 'Daejeon': '대전광역시',
 'Ulsan': '울산광역시',
 'Sejongsi': '세종특별자치시',
 'Gyeonggi-do': '경기도',
 'Gangwon-do': '강원도',
 'Chungcheongbuk-do': '충청북도',
 'Chungcheongnam-do': '충청남도',
 'Jeollabuk-do': '전라북도',
 'Jeollanam-do': '전라남도',
 'Gyeongsangbuk-do': '경상북도',
 'Gyeongsangnam-do': '경상남도',
 'Jeju-do': '제주특별자치도'}

(3) df에 변환을 수행하여 영어지명을 한글지명으로 변환

df.assign(
    시도 = df.시도.map({l['properties']['name_eng']:l['properties']['name'] for l in global_dict['features']})
)
년도 시도 지역 건물동수 연면적 에너지사용량(TOE)/전기 에너지사용량(TOE)/도시가스 에너지사용량(TOE)/지역난방
0 2018 서울특별시 종로구 17929 9141777 64818 82015 111
1 2018 서울특별시 중구 10598 10056233 81672 75260 563
2 2018 서울특별시 용산구 17201 10639652 52659 85220 12043
3 2018 서울특별시 성동구 14180 11631770 60559 107416 0
4 2018 서울특별시 광진구 21520 12054796 70609 130308 0
... ... ... ... ... ... ... ... ...
995 2019 제주특별자치도 서귀포시 34729 7233931 34641 1306 0
996 2020 제주특별자치도 제주시 66504 19819923 99212 22179 0
997 2020 제주특별자치도 서귀포시 34880 7330040 35510 1639 0
998 2021 제주특별자치도 제주시 67053 20275738 103217 25689 0
999 2021 제주특별자치도 서귀포시 35230 7512206 37884 2641 0

1000 rows × 8 columns

(4) local_dictglobal_dict의 지명정보를 정리하여 데이터프레임으로 만듦

# 예비학습

pd.DataFrame(
    [{'X':100,'y':0},
     {'X':101,'y':1}]
)    
X y
0 100 0
1 101 1

#

df_local = pd.DataFrame([l['properties'] for l in local_dict['features']])\
.drop(['name_eng','base_year'],axis=1)
df_local
name code
0 종로구 11010
1 중구 11020
2 용산구 11030
3 성동구 11040
4 광진구 11050
... ... ...
245 함양군 38380
246 거창군 38390
247 합천군 38400
248 제주시 39010
249 서귀포시 39020

250 rows × 2 columns

df_global = pd.DataFrame([l['properties'] for l in global_dict['features']])\
.drop(['name_eng','base_year'],axis=1)
df_global
name code
0 서울특별시 11
1 부산광역시 21
2 대구광역시 22
3 인천광역시 23
4 광주광역시 24
5 대전광역시 25
6 울산광역시 26
7 세종특별자치시 29
8 경기도 31
9 강원도 32
10 충청북도 33
11 충청남도 34
12 전라북도 35
13 전라남도 36
14 경상북도 37
15 경상남도 38
16 제주특별자치도 39

(5) loc의 에서 “전주시완산구”와 같이 정리된 지명들을 “완산구”로 변환

_dct = {name:name.split('시')[-1] for name in df_local.name if ("시" in name) and ("구" in name) and len(name)>3}
_dct 
{'수원시장안구': '장안구',
 '수원시권선구': '권선구',
 '수원시팔달구': '팔달구',
 '수원시영통구': '영통구',
 '성남시수정구': '수정구',
 '성남시중원구': '중원구',
 '성남시분당구': '분당구',
 '안양시만안구': '만안구',
 '안양시동안구': '동안구',
 '안산시상록구': '상록구',
 '안산시단원구': '단원구',
 '고양시덕양구': '덕양구',
 '고양시일산동구': '일산동구',
 '고양시일산서구': '일산서구',
 '용인시처인구': '처인구',
 '용인시기흥구': '기흥구',
 '용인시수지구': '수지구',
 '청주시상당구': '상당구',
 '청주시서원구': '서원구',
 '청주시흥덕구': '흥덕구',
 '청주시청원구': '청원구',
 '천안시동남구': '동남구',
 '천안시서북구': '서북구',
 '전주시완산구': '완산구',
 '전주시덕진구': '덕진구',
 '포항시남구': '남구',
 '포항시북구': '북구',
 '창원시의창구': '의창구',
 '창원시성산구': '성산구',
 '창원시마산합포구': '마산합포구',
 '창원시마산회원구': '마산회원구',
 '창원시진해구': '진해구'}
df_local.set_index('name').rename(_dct).reset_index()
name code
0 종로구 11010
1 중구 11020
2 용산구 11030
3 성동구 11040
4 광진구 11050
... ... ...
245 함양군 38380
246 거창군 38390
247 합천군 38400
248 제주시 39010
249 서귀포시 39020

250 rows × 2 columns

(6) df_localdf_global의 정보를 정리하여 merge, 합쳐진 정보를 df_json에 저장

df_json = df_local.set_index('name').rename(_dct).reset_index()\
.rename({'code':'code_local','name':'name_local'},axis=1)\
.assign(code = lambda df: df.code_local.str[:2])\
.merge(df_global)
df_json 
name_local code_local code name
0 종로구 11010 11 서울특별시
1 중구 11020 11 서울특별시
2 용산구 11030 11 서울특별시
3 성동구 11040 11 서울특별시
4 광진구 11050 11 서울특별시
... ... ... ... ...
245 함양군 38380 38 경상남도
246 거창군 38390 38 경상남도
247 합천군 38400 38 경상남도
248 제주시 39010 39 제주특별자치도
249 서귀포시 39020 39 제주특별자치도

250 rows × 4 columns

(7) df_json과 df의 정보를 merge하기 위하여 ’서울특별시-종로구’와 같은 형식으로 공통열을 각각 생성. 생성된 공통열의 원소가 일치하는지 비교

s1 = df_json.assign(on = lambda df: df.name + '-' + df.name_local)['on']
s2 = df.assign(
    시도 = df.시도.map({l['properties']['name_eng']:l['properties']['name'] for l in global_dict['features']})
).assign(on = lambda df: df.시도 + '-' + df.지역)['on']
set(s1)-set(s2), set(s2)-set(s1)
({'인천광역시-남구'}, {'인천광역시-미추홀구'})

(8) 아래의 기사를 살펴보고 지역명을 적절히 변환

df_json.assign(on = lambda df: df.name + '-' + df.name_local)\
.set_index('on').rename({'인천광역시-남구':'인천광역시-미추홀구'}).reset_index()\
.loc[:,['on','code','code_local']]
on code code_local
0 서울특별시-종로구 11 11010
1 서울특별시-중구 11 11020
2 서울특별시-용산구 11 11030
3 서울특별시-성동구 11 11040
4 서울특별시-광진구 11 11050
... ... ... ...
245 경상남도-함양군 38 38380
246 경상남도-거창군 38 38390
247 경상남도-합천군 38 38400
248 제주특별자치도-제주시 39 39010
249 제주특별자치도-서귀포시 39 39020

250 rows × 3 columns

(9) 데이터프레임을 결합

df_left = df_json.assign(on = lambda df: df.name + '-' + df.name_local)\
.set_index('on').rename({'인천광역시-남구':'인천광역시-미추홀구'}).reset_index()\
.loc[:,['on','code','code_local']]
df_right = df.assign(
    시도 = df.시도.map({l['properties']['name_eng']:l['properties']['name'] for l in global_dict['features']})
).assign(on = lambda df: df.시도 + '-' + df.지역)
df2 = pd.merge(df_left,df_right).drop('on',axis=1).set_index(['시도','지역','년도']).reset_index()
df2
시도 지역 년도 code code_local 건물동수 연면적 에너지사용량(TOE)/전기 에너지사용량(TOE)/도시가스 에너지사용량(TOE)/지역난방
0 서울특별시 종로구 2018 11 11010 17929 9141777 64818 82015 111
1 서울특별시 종로구 2019 11 11010 17851 9204140 63492 76653 799
2 서울특별시 종로구 2020 11 11010 17638 9148895 60123 71263 912
3 서울특별시 종로구 2021 11 11010 22845 18551145 125179 117061 0
4 서울특별시 중구 2018 11 11020 10598 10056233 81672 75260 563
... ... ... ... ... ... ... ... ... ... ...
995 제주특별자치도 제주시 2021 39 39010 67053 20275738 103217 25689 0
996 제주특별자치도 서귀포시 2018 39 39020 34154 6914685 34470 1597 0
997 제주특별자치도 서귀포시 2019 39 39020 34729 7233931 34641 1306 0
998 제주특별자치도 서귀포시 2020 39 39020 34880 7330040 35510 1639 0
999 제주특별자치도 서귀포시 2021 39 39020 35230 7512206 37884 2641 0

1000 rows × 10 columns

C. 시각화 (2018년도 전기에너지 사용량)

px.choropleth_mapbox(
    geojson = local_dict,
    featureidkey = 'properties.code',
    data_frame = df2.query('년도==2018'),
    locations = 'code_local',
    color='에너지사용량(TOE)/전기',
    hover_data=['시도','지역'],
    #---#
    mapbox_style="carto-positron",
    center={"lat": 36, "lon": 127.5}, 
    zoom=6,
    height=800,
    width=800
)

D. 시각화 (2018~2021년도 전기에너지 사용량)

seoul_dict = local_dict.copy() 
seoul_dict['features'] = [l for l in local_dict['features'] if l['properties']['code'][:2] == '11']
px.choropleth_mapbox(
    geojson = seoul_dict,
    featureidkey = 'properties.code',
    data_frame = df2,
    locations = 'code_local',
    color='에너지사용량(TOE)/전기',
    hover_data=['시도','지역'],
    animation_frame='년도',
    #---#
    mapbox_style="carto-positron",
    range_color=[0,400000],
    center={"lat": 37.5665, "lon": 126.9780},
    zoom=9,
    height=500,
    width=700,
)